package com.example.opengldemo.jbulletJava;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.vecmath.Vector3f;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.util.Log;
import android.view.MotionEvent;

import com.bulletphysics.BulletGlobals;
import com.bulletphysics.collision.broadphase.AxisSweep3;
import com.bulletphysics.collision.dispatch.CollisionConfiguration;
import com.bulletphysics.collision.dispatch.CollisionDispatcher;
import com.bulletphysics.collision.dispatch.DefaultCollisionConfiguration;
import com.bulletphysics.collision.shapes.BoxShape;
import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.dynamics.constraintsolver.HingeConstraint;
import com.bulletphysics.dynamics.constraintsolver.SequentialImpulseConstraintSolver;
import com.bulletphysics.linearmath.Clock;
import com.bulletphysics.linearmath.MatrixUtil;
import com.bulletphysics.linearmath.Transform;
import com.example.opengldemo.R;
import com.example.opengldemo.util.MatrixState;
import com.example.opengldemo.util.ShaderManager;

import static com.example.opengldemo.jbulletJava.Constant.*;

public class MySurfaceView2 extends GLSurfaceView {

    DiscreteDynamicsWorld dynamicsWorld;
    CollisionShape boxShape;
    HingeConstraint constraint;
    private float time;
    private float cyclePeriod;
    private float muscleStrength;
    Clock clock = new Clock();
    private float mPreviousY;
    private float mPreviousX;
    boolean flag = true;
    SceneRenderer renderer;

    private class SceneRenderer implements Renderer {
        Cuboid door1;
        Cuboid door2;
        int activeTexId;

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            GLES30.glEnable(GLES30.GL_DEPTH_TEST);

            ShaderManager.loadDefalutShader(getResources());
            ShaderManager.compileDefalutShader();

            MatrixState.setInitStack();

            activeTexId = initTexture(R.drawable.wood_bin1);

            door1 = new Cuboid(dynamicsWorld, boxShape, 0, 0, 0, 4, MySurfaceView2.this, halfX, halfY, halfZ);
            door2 = new Cuboid(dynamicsWorld, boxShape, 1, 0, 0, -4, MySurfaceView2.this, halfX, halfY, halfZ);

            addHingeConstraint(door1.body, door2.body);
            new Thread() {
                public void run() {
                    while (flag) {
                        try {
                            dynamicsWorld.stepSimulation(1f / 60.f, 5);
                            Thread.sleep(20);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {

            GLES30.glViewport(0, 0, width, height);

            float ratio = (float) width / height;

            MatrixState.setProjectFrustum(-ratio, ratio, -1, 1, 1.5f, 100);
        }

        @Override
        public void onDrawFrame(GL10 gl) {
            GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
            float ms = getDeltaTimeMicroseconds();
            time += ms;
            Log.e("onDrawFrame", "ms > " + ms + "  time > " + time);
            float preAngle = constraint.getHingeAngle(); //获取当前的旋转角度值
            float x = (((int) (time / 1000) % (int) (cyclePeriod)) / cyclePeriod) * BulletGlobals.SIMD_2_PI;
            float y = 0.5f * (1.0f + (float) Math.sin(x));
            Log.e("onDrawFrame", "x > " + x + "  y > " + y);
            float currAngle = constraint.getLowerLimit() + y * (constraint.getUpperLimit() - constraint.getLowerLimit());
            float angleDelta = currAngle - preAngle;
            float desiredAngularVel = 1000000.f * angleDelta / ms;
            Log.e("onDrawFrame", "angleDelta > " + angleDelta + "  currAngle > " + currAngle + "  desiredAngularVel > " + desiredAngularVel);
            constraint.enableAngularMotor(true, desiredAngularVel, muscleStrength);
            MatrixState.setCamera(cx, cy, cz, 0, 0, 0, 0f, 1.0f, 0.0f);
            MatrixState.pushMatrix();
            door1.drawSelf(activeTexId);
            door2.drawSelf(activeTexId);
            MatrixState.popMatrix();
        }

    }

    public MySurfaceView2(Context context) {
        super(context);
        this.setEGLContextClientVersion(3);
        renderer = new SceneRenderer();
        this.setRenderer(renderer);
        this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
        initWorld();
        time = 0.0f;
        cyclePeriod = 3000.0f;
        muscleStrength = 5f;
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        float y = e.getY();
        float x = e.getX();
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dy = y - mPreviousY;
                float dx = x - mPreviousX;
                if (Math.abs(dx) > 10) {
                    yAngle += dx / 100;
                    cx = (float) (Math.cos(yAngle) * 15);
                    cz = (float) (Math.sin(yAngle) * 15);
                }
                if (Math.abs(dy) > 10) {
                    cy = cy + dy / 10;
                    if (cy > 15) {
                        cy = 15;
                    } else if (cy < -15) {
                        cy = -15;
                    }
                }
                break;
        }
        mPreviousY = y;
        mPreviousX = x;
        return true;
    }

    public void initWorld() {
        CollisionConfiguration collisionConfiguration = new DefaultCollisionConfiguration();
        CollisionDispatcher dispatcher = new CollisionDispatcher(collisionConfiguration);
        Vector3f worldAabbMin = new Vector3f(-10000, -10000, -10000);
        Vector3f worldAabbMax = new Vector3f(10000, 10000, 10000);
        int maxProxies = 1024;
        AxisSweep3 overlappingPairCache = new AxisSweep3(worldAabbMin, worldAabbMax, maxProxies);
        SequentialImpulseConstraintSolver solver = new SequentialImpulseConstraintSolver();
        dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
        dynamicsWorld.setGravity(new Vector3f(0, -10, 0));
        boxShape = new BoxShape(new Vector3f(halfX, halfY, halfZ));
    }


    public void addHingeConstraint(RigidBody rbA, RigidBody rbB) {
        Transform transA = new Transform();
        transA.setIdentity();
        MatrixUtil.setEulerZYX(transA.basis, BulletGlobals.SIMD_HALF_PI, 0, 0);
        transA.origin.set(new Vector3f(0, 0, -halfZ));

        Transform transB = new Transform();
        transB.setIdentity();
        MatrixUtil.setEulerZYX(transB.basis, BulletGlobals.SIMD_HALF_PI, 0, 0);
        transB.origin.set(new Vector3f(0, 0, halfZ));

        constraint = new HingeConstraint(rbA, rbB, transA, transB);
        constraint.setLimit(-BulletGlobals.SIMD_HALF_PI / 2, BulletGlobals.SIMD_HALF_PI / 2);
        dynamicsWorld.addConstraint(constraint, true);
    }

    public int initTexture(int drawableId)//
    {

        int[] textures = new int[1];
        GLES30.glGenTextures
                (
                        1,
                        textures,
                        0
                );
        int textureId = textures[0];
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
        GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);


        InputStream is = this.getResources().openRawResource(drawableId);
        Bitmap bitmapTmp;
        try {
            bitmapTmp = BitmapFactory.decodeStream(is);
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


        GLUtils.texImage2D
                (
                        GLES30.GL_TEXTURE_2D,
                        0,
                        bitmapTmp,
                        0
                );
        bitmapTmp.recycle();

        return textureId;
    }

    public float getDeltaTimeMicroseconds() {
        float dt = clock.getTimeMicroseconds();
        clock.reset();
        return dt;
    }

}
